package pt.isel.pdm.ouryamba;

import java.util.LinkedList;

import android.app.Activity;

public class Waiter 
{
	/**
	 * Is the object which represent an entity waiting for a notification.</br>
	 * Contains a boolean variable that tells whether or not this was notified.</br>
	 * Contains a Activity variable that represents the Activity where the callback should be called
	 * (through {@code runOnUiThread()} method call).</br>
	 * Contains a Runnable variable that represents the callback that should run upon the notification. 
	 * */
	private class Wobject
	{
		boolean notified = false;
		Activity activity = null;
		Runnable run = null;
	}
	
	private LinkedList<Wobject> waiting;
	
	/**
	 * Creates the new list of {@code Wobject} elements.
	 * 
	 * @see Wobject
	 * */
	public Waiter()
	{
		waiting = new LinkedList<Wobject>();
	}
	
	/**
	 * Performs a wait (for a notification) in the same thread that called this method.
	 * */
	public void Wait()
	{
		Wobject o = new Wobject();
		synchronized(this)
		{
			waiting.add(o);
		}
		synchronized(o)
		{
			while(o.notified == false)
			{
				try
				{
					o.wait();
				} 
				catch (Exception e) 
				{
					
				}
			}
		}
	}
	
	/**
	 * Performs a wait on other thread.
	 * 
	 * @param activity Is the Activity which should be used to call {@code runOnUiThread(Runnable)}
	 * 		method. Can be sent as null and that case the Runnable callback will be executed on 
	 * 		other thread available.
	 * @param run Should contain all the code that should be executed upon the notification. If the
	 *  	code needs to executed on the UI thread, there should be specified the {@code activity}
	 *  	parameter. 
	 */
	public void Wait(Activity activity, Runnable run)
	{
		Wobject o = new Wobject();
		o.activity = activity;
		o.run = run;
		synchronized(this)
		{
			waiting.add(o);
		}
	}
	
	/**
	 * Notifies all the waiters until the moment of this call. Future waiters arrived during the 
	 * execution of this method will not be notified on this run.</br>
	 * 
	 * If the waiter had not specified it's Runnable variable, it is simply notified. Otherwise, if
	 * the Runnable is specified and the activity was not, the Runnable is executed in any available
	 * thread. If both are specified the Runnable is executed on the UI thread from the Activity.
	 * */
	public void NotifyAll()
	{
		LinkedList<Wobject> prev = null;
		synchronized(this)
		{
			prev = waiting;
			waiting = new LinkedList<Wobject>();
		}
		Wobject obj;
		while(prev.size() > 0)
		{
			obj = prev.remove();
			obj.notified = true;
			if(obj.run == null)
			{
				synchronized(obj)
				{
					obj.notify();
				}
			}
			else
			{
				if(obj.activity == null)
				{
					obj.run.run();
				}
				else
				{
					obj.activity.runOnUiThread(obj.run);
				}
			}
		}
	}

}
